package com.lancoo.cvas.arrangecourse.rulecheck;

import com.lancoo.cvas.arrangecourse.common.StrUtil;
import com.lancoo.cvas.arrangecourse.entities.BaseRule;
import com.lancoo.cvas.arrangecourse.entities.ClassHour;
import com.lancoo.cvas.arrangecourse.entities.TeachingClassUnit;
import com.lancoo.cvas.arrangecourse.entities.TimeslotRoom;
import lombok.extern.slf4j.Slf4j;

import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors;

/**
 * @Description 基础规则的满足
 * @Author Fortysun
 * @Date 2023/8/24 9:57
 **/
@Slf4j
public class BaseRuleCheck extends RuleCheck implements RuleDecorator {

    //接收父类的规则服务对象
    private RuleService ruleService;
    public BaseRuleCheck(RuleService ruleService, BaseRule baseRule) {
        this.ruleService = ruleService;
        super.baseRule = baseRule;
    }
    /**
     * 检测是否适合分配：
     * 1、判断是否满足课程分散、集中规则
     * 2、判断是否满足教师分散、集中规则
     * 3、判断是否满足教师单日最大节次安排
     * @param teachingClassUnit 教学班排课单元
     * @param continuousList 连上的教室课时分组信息列表
     * @param classHourMap 课表课时map
     * @return
     */
    @Override
    public Boolean isSuitable(TeachingClassUnit teachingClassUnit,
                              LinkedList<TimeslotRoom> continuousList,
                              ConcurrentHashMap<String, CopyOnWriteArrayList<ClassHour>> classHourMap) {
        //如果上一个增强的规则是符合要求的
        if (ruleService.isSuitable(teachingClassUnit, continuousList, classHourMap)) {
            //进行基础规则的检测
            return baseRuleSuitable(teachingClassUnit, continuousList, classHourMap);
        }

        return false;
    }

    /**
     * 检测是否适合分配：
     * 1、判断是否满足课程分散、集中规则
     * 2、判断是否满足教师分散、集中规则
     * 3、判断是否满足教师单日最大节次安排
     * @param teachingClassUnit 教学班排课单元
     * @param continuousList 连上的教室课时分组信息列表
     * @param classHourMap 课表课时map
     * @return
     */
    private boolean baseRuleSuitable(TeachingClassUnit teachingClassUnit,
                                     LinkedList<TimeslotRoom> continuousList,
                                     ConcurrentHashMap<String, CopyOnWriteArrayList<ClassHour>> classHourMap) {
        //如果连上的教室课时分组列表信息为空，或者没有内容
        if (continuousList == null || continuousList.isEmpty()) {
//            log.error("#BaseRuleCheck.baseRuleSuitable#:分配的教室课时分组信息列表为空~！！");
            return false;
        }
        //获取列表的第一个教室课时分组对象
        TimeslotRoom timeslotRoom = continuousList.get(0);

        //天编号，01周一、02周二。。。以此类推
        String dayNo = timeslotRoom.getDayNo();
        //半天编号，1上午、2下午、3晚上
        Integer periodType = timeslotRoom.getPeriodType();


        //教学班上课的教师id，多个以逗号分隔
        String teacherIds = teachingClassUnit.getTeacherIds();
        //教学班id
        Long teachingClassId = teachingClassUnit.getTeachingClassId();
        //连上节次
        Integer connectSection = teachingClassUnit.getConnectSection();

        List<ClassHour> preNextDayList = null;
        if (!baseRule.getCourseDistribution().equals("a")) {
            //1、处理课程教学班分散集中，需要获取当天，前后两天的已排课表数据
            preNextDayList = getPreNext2DayList(dayNo, classHourMap);
        }

        //2、处理教室每天的最大上课节数限制，需要获取当天的已排课表数据
        List<ClassHour> curDayList = getCurDayList(dayNo, classHourMap);
        //3、处理教师上课的集中分散，需要获取与待分配的教室课时分组信息相同的，半天内的数据
        List<ClassHour> halfDayList = curDayList.stream()
                .filter(classHour -> classHour.getTimeslotRoom().getPeriodType().equals(periodType))
                .collect(Collectors.toList());

        if (
            //课程分配，集中or 分散
            isCourseDistributionSuitable(baseRule.getCourseDistribution(), teachingClassId, preNextDayList)
                    //是否超过了，每个教师每天的最大节次
                    &&!isMaxSectionOverflow(baseRule.getMaxSection(), teacherIds, connectSection, curDayList)
                    //教师排课，集中or分散
                    && isTeacherDistributionSuitable(baseRule.getTeacherDistribution(),
                    teacherIds, connectSection, halfDayList)) {

            return true;
        }

        return false;
    }

    /**
     * 检测教师上课是集中or分散
     * 即，半天内如果安排同样的教学班，就是集中，否则就是分散
     * @param teacherDistribution a：系统无需求，b：集中，c：分散
     * @param teacherIds 待分配教学班的教师id，多个以逗号分隔
     * @param connectSection 课程教学班的连上节次
     * @param halfDayList 待分配的教室课时分组，相同半天的，已排课表信息列表
     * @return
     */
    protected boolean isTeacherDistributionSuitable(String teacherDistribution,
                                                    String teacherIds,
                                                    Integer connectSection,
                                                    List<ClassHour> halfDayList) {

        //如果已排课表没数据，那肯定不管分散和集中都是返回true的
        //因为此时没有对比数据
        if (halfDayList == null || halfDayList.isEmpty()) {
            return true;
        }

        //如果连上节次大于2，不存在说，集中在半天的的问题，
        //不需要判断，直接返回true，教师分布是符合要求的
        if (connectSection > 2) {
            return true;
        }
        //系统无要求
        if (!teacherDistribution.equals("c")) {
            //a:系统无需求，b：集中
            //如果是b，半天内这个老师有课，属于集中。半天没有课也算集中，所以直接返回true
            return true;
        } else {//c：分散
            //半天内有课，再安排，不属于分散
            //半天内没课，安排属于分散
            return isTeacherScheduleScatter(teacherIds, halfDayList);
        }
    }

    /**
     * 教师上课时间是否分散
     * @param teacherIds 教师id字符串，多个以逗号分隔
     * @param halfDayList 与待分配的教师课时分组信息，同处于同一个半天的已排课，课表数据
     * @return
     */
    private Boolean isTeacherScheduleScatter(String teacherIds,
                                             List<ClassHour> halfDayList) {
        //定义返回值，true分散，false属于集中
        Boolean bRet = true;

        //遍历前一天和后一天的已排课，课表信息列表进行比对
        for (ClassHour classHour : halfDayList) {
            //如果前一天或者后一天，存在相同的教学班
            if (StrUtil.isStrClashes(classHour.getCourseUnit().getTeacherIds(), teacherIds)) {
//                log.info("#BaseRuleCheck.isTeacherScheduleScatter#:不满足教师分散规则~");
                //那么再去进行安排当前待分配的教室课时分组信息，就是属于集中安排了
                bRet = false;
                break;
            }
        }


        return bRet;
    }


    /**
     * 检测课程安排是集中or分散，是否符合要求
     * @param courseDistribution 课程分布字符串
     * @param teachingClassId 待分配的教学班的id
     * @param preNextDayList 待分配教室课时分组，所在天的，前一天和后一天的已排课表
     * @return
     */
    protected Boolean isCourseDistributionSuitable(String courseDistribution,
                                                   Long teachingClassId,
                                                   List<ClassHour> preNextDayList) {
        //如果已排课表没数据，那肯定不管分散和集中都是返回true的
        //因为此时没有对比数据
        if (preNextDayList == null || preNextDayList.isEmpty()) {
            return true;
        }

        //系统无要求
        if (!courseDistribution.equals("c")) {
            //a:系统无需求，b：集中
            //如果是b，相邻两天内这个课程有课，属于集中。两天内没有课也算集中，所以直接返回true
            return true;
        } else {//c：分散
            return isCourseScheduleScatter(teachingClassId, preNextDayList);
        }

    }

    /**
     * 相邻两天内，这个教学班是否排过课，排过再去安排今天当前的这个教室课时分组，属于集中
     * 没排过，安排今天当前的这个教室课时分组，属于分散
     * @param teachingClassId 待安排的教学班id
     * @param preNextDayList 前后两天的已排课的课表信息
     * @return
     */
    private Boolean isCourseScheduleScatter(Long teachingClassId,
                                            List<ClassHour> preNextDayList) {
        //定义返回值， false代表不分散，即集中，true代表分散
        Boolean bRet = true;

        //遍历前一天和后一天的已排课，课表信息列表进行比对
        for (ClassHour classHour : preNextDayList) {
            //如果前一天或者后一天，存在相同的教学班
            if (classHour.getCourseUnit().getTeachingClassId().equals(teachingClassId)) {
//                log.info("#BaseRuleCheck.isCourseScheduleScatter#:不满足课程分散规则~");
                //那么再去进行安排当前待分配的教室课时分组信息，就是属于集中安排了
                bRet = false;
                break;
            }
        }

        return bRet;
    }

    /**
     * 判断是否课程超出设置的，教师每天的最大上课节次
     * @param maxSection 设置的最大上课节次
     * @param teacherIds 待分配的教师班排课单元对象
     * @param connectSection 已经分配教师课时分组的，课表信息对象列表
     * @param curDayList 即将分配给教学班排课单元的，教师课时分组信息列表数据
     * @return
     */
    protected Boolean isMaxSectionOverflow(Integer maxSection,
                                           String teacherIds,
                                           Integer connectSection,
                                           List<ClassHour> curDayList) {
        //定义返回值类型，默认不超出限制
        Boolean bRet = false;
        //如果不存在当天的已排课数据，那说名是教师在当天第一次排课，
        //符合要求，直接返回false，没有超出要求
        if (curDayList == null || curDayList.isEmpty()) {
            return bRet;
        }

        //找出符合条件的，待分配的半天内的课时列表信息
        List<ClassHour> classHourList = curDayList.stream()
                //筛选指定的教师
                .filter(classHour -> StrUtil.isStrClashes(classHour.getCourseUnit().getTeacherIds(), teacherIds))
                .collect(Collectors.toList());

        if (classHourList != null && !classHourList.isEmpty()) {
            //统计当前教师，当日的上课课节数
            Integer sectionNum = classHourList.size();
            //如果设置当前找到的教室课时分组，将超过设置的单日上课上限
            if (sectionNum + connectSection > maxSection) {
//                log.info("#BaseRuleCheck.isMaxSectionOverflow#:超过设置的单个教师，单日授课数量上限~");
                //超过上限，返回true;
                bRet = true;
            } else {
                //不超过上限，返回false,可以进行设置
                bRet = false;
            }

        } else {//当前指定的这天，这个教学班，都没有排课
            bRet = false;
        }
        //置空操作，方便gc回收
        if (classHourList != null
                && !classHourList.isEmpty()) {
            classHourList.clear();
            classHourList=null;
        }

        return bRet;
    }
}

